using System;
using EnvDTE;
using EnvDTE80;
using VSLangProj;

namespace VSSDKAssist.Assets.Helpers
{
	/// <summary>
	/// Visual Studio FileCodeModel helper class
	/// </summary>
	public static class FileCodeModelHelper
	{
		#region Public Implementation
		/// <summary>
		/// Gets the active code element.
		/// </summary>
		/// <param name="vs">The vs.</param>
		/// <param name="fileCodeModel">The file code model.</param>
		/// <param name="codeElementType">Type of the code element.</param>
		/// <returns></returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static object GetActiveCodeElement(DTE vs, FileCodeModel fileCodeModel, Type codeElementType)
		{
			Guard.ArgumentNotNull(vs, "vs");
			Guard.ArgumentNotNull(fileCodeModel, "fileCodeModel");
			Guard.ArgumentNotNull(codeElementType, "codeElementType");

			TextSelection textSelection = (TextSelection)vs.ActiveDocument.Selection;
			TextPoint point = textSelection.ActivePoint;

			object element;

			if(codeElementType.IsAssignableFrom(typeof(CodeNamespace)))
			{
				try
				{
					element = (CodeNamespace)fileCodeModel.CodeElementFromPoint(
								point,
								vsCMElement.vsCMElementNamespace);
					return element;
				}
				catch
				{
					return null;
				}
			}
			else if(codeElementType.IsAssignableFrom(typeof(CodeAttribute)))
			{
				try
				{
					element = (CodeAttribute)fileCodeModel.CodeElementFromPoint(
								point,
								vsCMElement.vsCMElementAttribute);
					return element;
				}
				catch
				{
					return null;
				}
			}
			else if(codeElementType.IsAssignableFrom(typeof(CodeClass)))
			{
				try
				{
					element = (CodeClass)fileCodeModel.CodeElementFromPoint(
								point,
								vsCMElement.vsCMElementClass);
					return element;
				}
				catch
				{
					return null;
				}
			}
			else if(codeElementType.IsAssignableFrom(typeof(CodeProperty)))
			{
				try
				{
					element = (CodeProperty)fileCodeModel.CodeElementFromPoint(
								point,
								vsCMElement.vsCMElementProperty);
					return element;
				}
				catch
				{
					return null;
				}
			}
			else if(codeElementType.IsAssignableFrom(typeof(CodeFunction)))
			{
				try
				{
					element = (CodeFunction)fileCodeModel.CodeElementFromPoint(
								point,
								vsCMElement.vsCMElementFunction);
					return element;
				}
				catch
				{
					return null;
				}
			}

			return null;
		}

		/// <summary>
		/// Determines whether the specified element has attribute.
		/// </summary>
		/// <param name="element">The element.</param>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <returns>
		/// 	<c>true</c> if the specified element has attribute; otherwise, <c>false</c>.
		/// </returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static bool HasAttribute(CodeClass element, string attributeName)
		{
			Guard.ArgumentNotNull(element, "element");
			Guard.ArgumentNotNull(attributeName, "attributeName");

			if(element.Attributes.Count > 0)
			{
				foreach(CodeElement att in element.Attributes)
				{
					if(att.Name.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase))
					{
						return true;
					}
				}
			}

			return false;
		}

		/// <summary>
		/// Adds the attribute.
		/// </summary>
		/// <param name="element">The element.</param>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <param name="attributeValue">The attribute value.</param>
		/// <param name="dontCreateIfExists">if set to <c>true</c> [dont create if exists].</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static void AddAttribute(CodeClass element, string attributeName, string attributeValue, bool dontCreateIfExists)
		{
			Guard.ArgumentNotNull(element, "element");
			Guard.ArgumentNotNullOrEmptyString(attributeName, "attributeName");
			Guard.ArgumentNotNullOrEmptyString(attributeValue, "attributeValue");

			if(dontCreateIfExists && !HasAttribute(element, attributeName))
			{
				return;
			}

			element.AddAttribute(attributeName, attributeValue, element.Attributes.Count);
		}

		/// <summary>
		/// Inserts the text to code element.
		/// </summary>
		/// <param name="codeElement">The code element.</param>
		/// <param name="text">The text.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static void InsertTextToCodeElement(CodeElement codeElement, string text)
		{
			Guard.ArgumentNotNull(codeElement, "codeElement");
			Guard.ArgumentNotNullOrEmptyString(text, "text");

			EditPoint startEditPoint = codeElement.StartPoint.CreateEditPoint();
			EditPoint editPoint = codeElement.EndPoint.CreateEditPoint();

			editPoint.MoveToLineAndOffset(editPoint.Line, 1);
			((EditPoint2)editPoint).InsertNewLine(1);
			editPoint.LineUp(1);
			editPoint.Insert(text);

			//Re Format inserted text
			startEditPoint.StartOfDocument();
			editPoint.EndOfDocument();

			startEditPoint.SmartFormat(editPoint);
		}

		/// <summary>
		/// Updates the attribute.
		/// </summary>
		/// <param name="attribute">The attribute.</param>
		/// <param name="attributeValue">The attribute value.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static void UpdateAttribute(CodeAttribute attribute, string attributeValue)
		{
			Guard.ArgumentNotNull(attribute, "attribute");
			Guard.ArgumentNotNull(attributeValue, "attributeValue");

			attribute.Value = attributeValue;
		}

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
        public static void RemoveAttribute(CodeAttribute2 attribute)
        {
            Guard.ArgumentNotNull(attribute, "attribute");

            attribute.Delete();
        }

		/// <summary>
		/// Gets the attribute.
		/// </summary>
		/// <param name="codeClass">The code class.</param>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <returns></returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static CodeAttribute GetAttribute(CodeClass codeClass, string attributeName)
		{
			Guard.ArgumentNotNull(codeClass, "codeClass");
			Guard.ArgumentNotNull(attributeName, "attributeName");

			if(codeClass.Attributes.Count > 0)
			{
				foreach(CodeElement att in codeClass.Attributes)
				{
					CodeAttribute codeAttribute = (CodeAttribute)att;

					if(att.Name.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase))
					{
						return codeAttribute;
					}
				}
			}

			return null;
		}

		/// <summary>
		/// Gets the code function.
		/// </summary>
		/// <param name="codeClass">The code class.</param>
		/// <param name="functionName">Name of the function.</param>
		/// <returns></returns>
		public static CodeFunction GetCodeFunction(CodeClass codeClass, string functionName)
		{
			CodeElementsIterator iterator = new CodeElementsIterator(codeClass as CodeElement);

			foreach(CodeElement element in iterator)
			{
				CodeFunction function = element as CodeFunction;

				if(function != null && (element.Name == functionName))
				{
					return function;
				}
			}

			return null;
		}

		/// <summary>
		/// Gets the attribute argument.
		/// </summary>
		/// <param name="attribute">The attribute.</param>
		/// <param name="attributeArgument">The attribute argument.</param>
		/// <returns></returns>
		public static CodeAttributeArgument GetAttributeArgument(CodeAttribute attribute, string attributeArgument)
		{
			CodeAttribute2 attribute2 = (EnvDTE80.CodeAttribute2)attribute;

			foreach(CodeAttributeArgument argument in attribute2.Arguments)
			{
				if(argument.Name.Equals(attributeArgument, StringComparison.InvariantCultureIgnoreCase))
				{
					return argument;
				}
			}
			return null;
		}

		/// <summary>
		/// Updates the code attribute argument.
		/// </summary>
		/// <param name="attributes">The attributes.</param>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <param name="argumentName">Name of the argument.</param>
		/// <param name="argumentValue">The argument value.</param>
		/// <returns></returns>
		public static bool UpdateCodeAttributeArgument(
			CodeElements attributes,
			string attributeName,
			string argumentName,
			string argumentValue)
		{
			return UpdateCodeAttributeArgument(attributes, attributeName, argumentName, argumentValue, true);
		}

		/// <summary>
		/// Updates the code attribute argument.
		/// </summary>
		/// <param name="attributes">The attributes.</param>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <param name="argumentName">Name of the argument.</param>
		/// <param name="argumentValue">The argument value.</param>
		/// <param name="createIfNew">if set to <c>true</c> [create if new].</param>
		/// <returns></returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static bool UpdateCodeAttributeArgument(
			CodeElements attributes,
			string attributeName,
			string argumentName,
			string argumentValue,
			bool createIfNew)
		{
			Guard.ArgumentNotNull(attributes, "attributes");
			Guard.ArgumentNotNullOrEmptyString(attributeName, "attributeName");
			Guard.ArgumentNotNullOrEmptyString(argumentName, "argumentName");
			Guard.ArgumentNotNullOrEmptyString(argumentValue, "argumentValue");

			bool result = false;
			foreach(CodeElement attribute in attributes)
			{
				CodeAttribute codeAttribute = (CodeAttribute)attribute;
				if(attribute.FullName.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase))
				{
					result = UpdateCodeAttributeArgument(codeAttribute, argumentName, argumentValue, createIfNew);
					break;
				}
			}

			return result;
		}

		/// <summary>
		/// Updates the code attribute argument.
		/// </summary>
		/// <param name="codeAttribute">The code attribute.</param>
		/// <param name="argumentName">Name of the argument.</param>
		/// <param name="argumentValue">The argument value.</param>
		/// <param name="createIfNew">if set to <c>true</c> [create if new].</param>
		/// <returns></returns>
		public static bool UpdateCodeAttributeArgument(
			CodeAttribute codeAttribute,
			string argumentName,
			string argumentValue,
			bool createIfNew)
		{
			Guard.ArgumentNotNull(codeAttribute, "codeAttribute");
			Guard.ArgumentNotNullOrEmptyString(argumentName, "argumentName");

			bool result = false;

			CodeAttribute2 attribute2 = (EnvDTE80.CodeAttribute2)codeAttribute;
			CodeAttributeArgument argumentMatch = null;
			foreach(CodeAttributeArgument argument in attribute2.Arguments)
			{
				if(argument.Name.Equals(argumentName, StringComparison.InvariantCultureIgnoreCase))
				{
					argumentMatch = argument;
					break;
				}
			}
			if(argumentMatch != null)
			{
				argumentMatch.Value = argumentValue;
				result = true;
			}
			else if(createIfNew)
			{
				attribute2.AddArgument(argumentValue, argumentName, attribute2.Arguments.Count);
				result = true;
			}

			return result;
		}

		/// <summary>
		/// Finds the name of the code element by type.
		/// </summary>
		/// <param name="project">The project.</param>
		/// <param name="typeName">Name of the type.</param>
		/// <param name="elementKind">Kind of the element.</param>
		/// <returns></returns>
		public static CodeElement FindCodeElementByTypeName(Project project, string typeName, vsCMElement elementKind)
		{
			CodeElement result = null;

			if(project == null ||
				string.IsNullOrEmpty(typeName))
			{
				return null;
			}

			foreach(ProjectItem projectItem in new ProjectItemIterator(project))
			{
				CodeElement element = FindCodeElementByTypeName(projectItem, typeName, elementKind);
				if(element != null)
				{
					result = element;
					break;
				}
			}

			if(result == null)
			{
				VSProject vsProject = project.Object as VSProject;
				foreach(Reference reference in vsProject.References)
				{
					result = FindCodeElementByTypeName(reference.SourceProject, typeName, elementKind);
					if(result != null &&
						result.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject)
					{
						break;
					}
				}
			}

			return result;
		}

		/// <summary>
		/// Finds the name of the code element by type.
		/// </summary>
		/// <param name="projectItem">The project item.</param>
		/// <param name="typeName">Name of the type.</param>
		/// <param name="elementKind">Kind of the element.</param>
		/// <returns></returns>
		public static CodeElement FindCodeElementByTypeName(
			ProjectItem projectItem, string typeName, vsCMElement elementKind)
		{
			foreach(CodeElement element in new CodeElementsIterator(projectItem))
			{
				if(element.Kind == vsCMElement.vsCMElementNamespace)
				{
					foreach(CodeElement type in ((CodeNamespace)element).Members)
					{
						CodeElement result = InspectCodeElement(type, typeName, elementKind);
						if(result != null)
						{
							return result;
						}
					}
				}
				else
				{
					CodeElement result = InspectCodeElement(element, typeName, elementKind);
					if(result != null)
					{
						return result;
					}
				}
			}
			return null;
		}
		#endregion

		#region Private Implementation
		private static CodeElement InspectCodeElement(
			CodeElement element, string targetFullName, vsCMElement elementKind)
		{
			if(element.IsCodeType)
			{
				if(element.Kind == elementKind &&
					element.FullName.Equals(targetFullName, StringComparison.InvariantCultureIgnoreCase))
				{
					return element;
				}
			}

			return null;
		}
		#endregion
	}
}